package lk.icta.common.dao.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.dao.DistrictDAO;
import lk.icta.common.dao.PreloadableDAO;
import lk.icta.common.entity.District;
import lk.icta.common.ErrorCodes;
import lk.icta.user.entity.User;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.*;

/**
 * @author Supun Nimesh Karunathilaka
 * @author Duminda Dharmakeerthi
 */
public class DistrictDAOImpl extends BaseDAO implements DistrictDAO, PreloadableDAO {

    private final Map<Integer, District> districtsByPK = new HashMap<Integer, District>();
    private List<District> districts;
    // local caches indexed by districtUKey and dsDivisionUKey
    private final Map<Integer, Map<Integer, String>> siNames = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, Map<Integer, String>> enNames = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, Map<Integer, String>> taNames = new HashMap<Integer, Map<Integer, String>>();

    @Transactional(propagation = Propagation.MANDATORY)
    public void addDistrict(District district, User user) {
        em.persist(district);
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void updateDistrict(District district, User user) {
        em.merge(district);
    }

    public Map<Integer, String> getDistrictNames(String language, User user) {
        return null;
    }

    /*public Map<Integer, String> getAllDistrictNames(String language, User user) {
        return null;
    }*/

    public String getNameByPK(int districtUKey, String language) {
        return null;
    }


    @Transactional(propagation = Propagation.SUPPORTS)
    public District getDistrictById(int id) {
        logger.debug("get District by id [Id: {}]", id);
        return districtsByPK.get(id);
    }

    @Override
    public List<District> getAllDistricts() {
        return districts;
    }

    public District getDistrictByCode(int districtId) {
        Query q = em.createNamedQuery("get.district.by.code");
        q.setParameter("districtId", districtId);
        try {
            return (District) q.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public List<District> findAll() {
        Query q = em.createNamedQuery("findAllDistricts");
        return q.getResultList();
    }

    /**
     * Loads all values from the database table into a cache
     */
    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public void preload() {

        Query query = em.createQuery("SELECT d FROM District d order by d.enDistrictName");
        districts = query.getResultList();

        Map<Integer, String> subMap = null;

        for (District d : districts) {
            updateCache(d);
        }

        logger.debug("Loaded : {} DSDivisions from the database", districts.size());
    }

    public Map<Integer, String> getAllDistrictNamesByProvinceId(int provinceId, String language, User user) {
        Map<Integer, String> result = null;
        if (AppConstants.SINHALA.equals(language)) {
            result = getDistrictNamesImpl(siNames, provinceId);
        } else if (AppConstants.ENGLISH.equals(language)) {
            result = getDistrictNamesImpl(enNames, provinceId);
        } else if (AppConstants.TAMIL.equals(language)) {
            result = getDistrictNamesImpl(taNames, provinceId);
        } else {
            handleException("Unsupported language : " + language, ErrorCodes.INVALID_LANGUAGE);
        }
        return result;
    }

    public Map<Integer, String> getAllDistrictNames(String language, User user) {
        Map<Integer, String> result = new HashMap();
        if (AppConstants.SINHALA.equals(language)) {
            for (int i = 1; i < (AppConstants.NO_OF_PROVINCE + 1); i++) {
                result.putAll(getDistrictNamesImpl(siNames, i));
            }
        } else if (AppConstants.ENGLISH.equals(language)) {
            for (int i = 1; i < (AppConstants.NO_OF_PROVINCE + 1); i++) {
                result.putAll(getDistrictNamesImpl(enNames, i));
            }
        } else if (AppConstants.TAMIL.equals(language)) {
            for (int i = 1; i <(AppConstants.NO_OF_PROVINCE + 1); i++) {
                result.putAll(getDistrictNamesImpl(taNames, i));
            }
        } else {
            handleException("Unsupported language : " + language, ErrorCodes.INVALID_LANGUAGE);
        }
        return result;
    }

    private Map<Integer, String> getDistrictNamesImpl(Map<Integer, Map<Integer, String>> namesByDistrict, int provinceId) {

        Map<Integer, String> dsDivisionNames = namesByDistrict.get(provinceId);
        if (dsDivisionNames != null) {
            return dsDivisionNames;
        }
        logger.warn("No DS Divisions found for District : {}", provinceId);
        return Collections.emptyMap();
    }

    private void updateCache(District d) {
        Map<Integer, String> subMap;
        int provinceId = d.getProvince().getId();
        int districtCode = d.getCode();
        int districtId = d.getId();

        districtsByPK.put(d.getId(), d);

        subMap = siNames.get(provinceId);
        if (subMap == null) {
            subMap = new TreeMap<Integer, String>();
            siNames.put(provinceId, subMap);
        }
        subMap.put(districtId, districtCode + " : " + d.getSiDistrictName());

        subMap = enNames.get(provinceId);
        if (subMap == null) {
            subMap = new TreeMap<Integer, String>();
            enNames.put(provinceId, subMap);
        }
        subMap.put(districtId, districtCode + " : " + d.getEnDistrictName());

        subMap = taNames.get(provinceId);
        if (subMap == null) {
            subMap = new TreeMap<Integer, String>();
            taNames.put(provinceId, subMap);
        }
        subMap.put(districtId, districtCode + " : " + d.getTaDistrictName());
    }
}
